package mypinger

import (
	"errors"
	"math"
	"net"
	"time"

	"github.com/digineo/go-ping"
	"gorm.io/gorm"

	"gitee.com/sillyman/PingHostsWebView/models"
	"gitee.com/sillyman/PingHostsWebView/modules/db"
)

// myPinger
type myPinger struct {
	// ipAddr 是目标地址
	ipAddr *net.IPAddr

	// mydbConn 数据库连接，此数据库存储此目标地址的ping记录
	mydbConn *gorm.DB

	// pingTimeout echo等待超时
	pingTimeout time.Duration

	// pingPayloadSize 荷载
	pingPayloadSize uint16

	// pingIntervalDur ping后休息的时长
	pingIntervalDur time.Duration

	// numberOfMaxRecord 最大记录数量
	numberOfMaxRecord uint

	// quitCh 退出标志
	quitCh chan bool
}

// 启动Ping
func (p *myPinger) MustStart() {
	for {
		select {
		case <-p.quitCh:
			// 关闭数据库连接
			sqlDB, _ := p.mydbConn.DB()
			_ = sqlDB.Close()

			return

		case <-time.After(p.pingIntervalDur):
			instance, err := ping.New("0.0.0.0", "::")
			if err != nil {
				panic(err)
			}

			instance.SetPayloadSize(p.pingPayloadSize)
			rtt, err := instance.Ping(p.ipAddr, p.pingTimeout)
			if err != nil {
				rtt = -1
			}
			instance.Close() // 关闭连接

			// 更新数据库
			p.mustUpdatePingSummary(rtt)
			p.mustWriteThisRecord(rtt)
		}
	}
}

// Destroy 销毁
func (p *myPinger) Destroy() {
	select {
	case <-p.quitCh:
	default:
		close(p.quitCh)
	}
}

// mustUpdatePingSummary 更新总结
func (p *myPinger) mustUpdatePingSummary(rtt time.Duration) {
	var summary models.PingSummary

	dbConn := db.MustObtainMainDB()
	defer db.ReleaseMainDB()

	if err := dbConn.Where("ip = ?", p.ipAddr.IP.String()).First(&summary).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return
		}
		panic(err)
	}

	summary.LastRTT = rtt

	if rtt >= 0 {
		summary.SucceedCount++

		summary.TotalRTT += rtt
		summary.AvgRTT = summary.TotalRTT / time.Duration(summary.SucceedCount)

		summary.SumRTTSquares += (rtt - summary.AvgRTT) * (rtt - summary.AvgRTT)
		summary.MDevRTT = time.Duration(math.Sqrt(float64(summary.SumRTTSquares / time.Duration(summary.SucceedCount))))

		if rtt > summary.MaxRTT || summary.MaxRTT == 0 {
			summary.MaxRTT = rtt
		}
		if rtt < summary.MinRTT || summary.MinRTT == 0 {
			summary.MinRTT = rtt
		}
	} else {
		summary.FailedCount++
		summary.LastFailedAt = time.Now()
	}

	if err := dbConn.Omit("comment").Save(&summary).Error; err != nil {
		panic(err)
	}
}

// writeThisRecord 记录本次ping到数据库
func (p *myPinger) mustWriteThisRecord(rtt time.Duration) {
	p.mydbConn.Create(&models.PingRecord{RTT: rtt})

	var count int64
	if err := p.mydbConn.Model(&models.PingRecord{}).Count(&count).Error; err != nil {
		panic(err)
	}
	if uint(count) > p.numberOfMaxRecord {
		var firstRec models.PingRecord
		if err := p.mydbConn.First(&firstRec).Error; err != nil {
			panic(err)
		}
		if err := p.mydbConn.Delete(&firstRec).Error; err != nil {
			panic(err)
		}
	}
}
